package org.openstreetmap.josm.plugins.cadastre_es;

import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.Locale;
import java.util.Vector;
import javax.imageio.ImageIO;
import javax.swing.JOptionPane;

import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.coor.LatLon;
import org.openstreetmap.josm.data.osm.DataSet;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.RelationMember;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.gui.layer.Layer;
import org.openstreetmap.josm.gui.layer.OsmDataLayer;
import org.openstreetmap.josm.plugins.cadastre_es.exceptions.NotClosedPolygon;
import org.openstreetmap.josm.plugins.cadastre_es.exceptions.NotPolygon;

public class Process extends Thread {
	private Vector<Polygon> polygons = new Vector<Polygon>();
	private VertexMap vertexMap = new VertexMap();
	private DataSet data;
	public static int id = 0;
    private static final int pixelsEachDegree = 1200000;
    private static final double degreesEachPixel = (double) 1 / pixelsEachDegree;
    private static String wmsUrl = "http://ovc.catastro.meh.es/Cartografia/WMS/ServidorWMS.aspx?SERVICE=WMS&SRS=EPSG:4326&REQUEST=GETMAP&bbox=%f,%f,%f,%f&width=%d&height=%d&format=PNG&transparent=No&layers=constru";
    private static int maxSize = 4000;
    private static boolean debug = true;
    private BufferedImage img;
    public static int width;
    public static int height;
    public static double bottomLat;
    public static double topLat;
    public static double leftLon;
    public static double rightLon;
	
	public void run() {
	    //Main.map.mapView.getGraphics().
	    //Main.main.getActiveLayer().get
        LatLon startLatlon = Main.map.mapView.getLatLon(0, Main.map.mapView.getHeight() - 1);
        LatLon endLatlon = Main.map.mapView.getLatLon(Main.map.mapView.getWidth() - 1, 0);
        
        bottomLat = startLatlon.getY();
        topLat = endLatlon.getY();
        leftLon = startLatlon.getX();
        rightLon = endLatlon.getX();
        
        if (debug)
            System.out.println("bottomLat = " + bottomLat + "; topLat = " + topLat + "; leftLon = " + leftLon + "; rightLon = "+rightLon);
        
        try {
            getImage(leftLon, bottomLat, rightLon, topLat);
        }
        catch (Exception e) {
            CadastrePlugin.progressFrame.dispose();
            JOptionPane.showMessageDialog(null, "Error downloading image, maybe it's too big, try with a smaller area.", "Error", JOptionPane.ERROR_MESSAGE);
            return;
        }
		Pixel.img = img;
		height = img.getHeight();
		width = img.getWidth();
		Polygon polygon;
		
		closeImage();
		float totalPixels = (long) height * (long) width;
		int leftSteps;
		// Detect buildings
		for(int y = 3; y < (height - 1 - Pixel.BOTTOM_MARGIN) ; y += 4) {
			for(int x = 3; x < (width - 1); x += 4) {
				Pixel p = new Pixel(x, y);
				if (p.isBuilding()) {
				    leftSteps = 0;
				    while (!p.left().isBorder()) {
				        p = p.left();
				        if (++leftSteps > 3) {
				            p = null;
				            break;
				        }
				    }
				    if (p == null)
				        continue;
					try {
					    float currentPixel = y * width + x;
					    CadastrePlugin.progressFrame.setProgress((int)(100f * (currentPixel / totalPixels)));
						polygon = new Polygon(p.left(), false);
						polygons.add(polygon);
						// Load vertexMap with vertexes found
						for (PolyPixel vertex : polygon.getVertexes()) 
							vertexMap.put(vertex);
						for (Polygon hole : polygon.getHoles()) {
							for (PolyPixel vertex : hole.getVertexes()) { 
								vertex.setId(++id);
								vertex.setRGB(Pixel.VERTEX);
							}
						}
						if (polygon.getVertexes().size() == 0)
							System.out.print("");
					}
					catch (NotPolygon e) {
					    if (debug)
					        System.out.println(e.getMessage());
					}
					catch (NotClosedPolygon e) {
						System.out.println(e.getMessage());
					}
					catch (java.lang.StackOverflowError e){
						System.out.println("Overflow Error at starting polygon pixel: " + p.left().x() + ", " + p.left().y());
					}
				}
			}
		}
		System.out.println(Polygon.buildings+" buildings found!");
		
		System.out.print("Joining near vertexes... ");
		// Join near vertexes
		for (Polygon pol : polygons) 
			for (PolyPixel vertex : pol.getVertexes())
				if (!vertex.isFinal())
					joinNearVertexes(vertex);
		System.out.println("done");
		
		System.out.print("Cleaning overlapped vertexes... ");
		for (Polygon pol : polygons) {
			int numVertexes = pol.getVertexes().size();
			for (int vnum = 0; vnum < numVertexes; vnum++) {
				int afterIndex = (vnum + 1) % numVertexes;
				PolyPixel after = pol.getVertexes().get(afterIndex);
				PolyPixel current = pol.getVertexes().get(vnum);
		
				if (current.equal(after) || current.distance(after) < 3) {
					pol.getVertexes().remove(vnum);
					current.setVertex(false);
					numVertexes--;
					vnum--;
					continue;
				}
			}
		}
		System.out.println("done");

		System.out.print("Joining near polygons... ");
		// Join near polygons
		PolyPixel start, current, nearVertex, nextVertex, lastVertex, newVertex, curMove, nearerPixel;
		int lastVertexIndex, minDistance, curDistance;
		for (Polygon pol : polygons) {
			if (pol.getVertexes().size() < 3)
				continue;
			start = pol.getVertexes().firstElement();
			if (start.x() == 241 && start.y() == 617)
				System.out.print("");
			lastVertex = start;
			current = start;
			do 
				current = current.getNext();
			while (current.equal(start));
			nextVertex = pol.getVertexes().get(1);
			lastVertexIndex = 0;
			
			//while (!current.equal(start) && !(!lastVertex.equal(start) && current.distance(start) < 6)) {
			while (!current.equal(start)) {
				if (current.x() == 7796 && current.y() == 2895)
					System.out.print("");
				if (current.equal(lastVertex)) {
					current = current.getNext();
					continue;
				}
				if (current.getVertex()) {
					lastVertex = current;
					lastVertexIndex++;
					if (pol.getVertexes().size() <= (lastVertexIndex+1)) { // If next vertex is first vertex
						lastVertexIndex = 0;
						nextVertex = pol.getVertexes().get(0);
					}
					else
						nextVertex = pol.getVertexes().get(lastVertexIndex + 1);
					
				}
				else if (current.distance(lastVertex) > 0 && current.distance(nextVertex) > 0) {
					nearVertex = getNearVertex(current, pol.getVertexes());
					if (nearVertex != null) { // Found a vertex from a near building
						curMove = current;
						nearerPixel = current;
						minDistance = Integer.MAX_VALUE;
						while (curMove.distance(nextVertex) > 3) {
							curDistance = curMove.distance(nearVertex);
							if (curDistance < minDistance) {
								nearerPixel = curMove;
								minDistance = curDistance; 
							}
							curMove = curMove.getNext();
						}
						if (minDistance < 3) {
							// Create new vertex from data of near vertex found
							newVertex = new PolyPixel(nearVertex);
							newVertex.setVertex(true);
							newVertex.setId(nearVertex.getId());
							
							// New links for new vertex
							newVertex.setPrev(nearerPixel);
							newVertex.setNext(nearerPixel.getNext());
							nearerPixel.getNext().setPrev(newVertex);
							nearerPixel.setNext(newVertex);
							
							if (lastVertexIndex == 0 && !lastVertex.equal(start))	// Append vertex
								pol.getVertexes().add(newVertex);
							else
								pol.getVertexes().add(++lastVertexIndex, newVertex);	// Insert vertex
							current = newVertex.getNext();
							lastVertex = newVertex;
							continue;
						}
						else {
							current = nearerPixel.getNext();
							continue;
						}
					}
				}
				current = current.getNext();
			}
		}
		System.out.println("done");

        System.out.print("Loading data... ");
		data = new DataSet();
		Node n, hn;
		Way w, hw;
		Relation r;
		for (Polygon pol : polygons) {
            if (pol.getVertexes().size() < 3)
                continue;
            w = new Way();
            for (PolyPixel vertex : pol.getVertexes()) {
                n = vertexMap.getNode(vertex);
                if (n == null) {
                    n = new Node();
                    n.setCoor(new LatLon(vertex.getLat(), vertex.getLon()));
                    data.addPrimitive(n);
                    vertexMap.setNode(vertex, n);
                }
                w.addNode(n);
            }
            w.addNode(w.firstNode());
            w.put("building", "yes");
            w.put("source", "catastro");
            data.addPrimitive(w);
            
            if (pol.getHoles().size() > 0) {
                r = new Relation();
                r.addMember(new RelationMember("outer", w));
                r.put("type", "multipolygon");
                for (Polygon hole : pol.getHoles()) {
                    hw = new Way();
                    for (PolyPixel vertex : hole.getVertexes()) {
                        hn = vertexMap.getNode(vertex);
                        if (hn == null) {
                            hn = new Node();
                            hn.setCoor(new LatLon(vertex.getLat(), vertex.getLon()));
                            data.addPrimitive(hn);
                            vertexMap.setNode(vertex, hn);
                        }
                        hw.addNode(hn);
                    }
                    hw.addNode(hw.firstNode());
                    data.addPrimitive(hw);
                    r.addMember(new RelationMember("inner", hw));
                }
                data.addPrimitive(r);
            }
        }
		
        Layer layer = new OsmDataLayer(data, "Imported buildings", null);
        Main.map.mapView.addLayer(layer);
        
        /*try {
            layer = new ImageLayer(img);
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }
        Main.map.mapView.addLayer(layer);*/
        
		System.out.println("done");
		CadastrePlugin.progressFrame.dispose();

        if (debug) {
            File outputfile = new File("final.png");
            try {
                ImageIO.write(img, "png", outputfile);
            } catch (IOException e) {
                e.printStackTrace();
            }
            //url = "file:/home/marc/workspace/cadastre_es/final3.png";
            //img = ImageIO.read(new URL(url));
        }
	}
	
	private void joinNearVertexes(PolyPixel vertex) {
		Vector<PolyPixel> nearVertexes = getNearVertexes(vertex);
		Pixel finalVertex;
		int x = 0;
		int y = 0;
		id++;
		
		if (nearVertexes.size() > 1) {
			for (PolyPixel nearVertex : nearVertexes) {
				x += nearVertex.x();
				y += nearVertex.y();
			}
			finalVertex = new Pixel(x/nearVertexes.size(), y/nearVertexes.size());
			for (PolyPixel nearVertex : nearVertexes) {
				nearVertex.setRGB(0xFF00FFFF);
				vertexMap.remove(nearVertex);
				nearVertex.copy(finalVertex);
				nearVertex.setId(id);
				nearVertex.setFinal(true);
				vertexMap.put(nearVertex);
			}
			finalVertex.setRGB(0xFF000000);
		}
		else {
			vertex.setId(id);
			vertex.setRGB(Pixel.VERTEX);
		}
		
	}
	
	private java.util.Vector<PolyPixel> getNearVertexes(PolyPixel v) {
		Vector<PolyPixel> nearVertexes = new Vector<PolyPixel>();
		Pixel cur;
		for (int dx = -3; dx < 4; dx++) {
			for (int dy = -3; dy < 4; dy++) {
				cur = new Pixel(v.x() + dx, v.y() + dy);
				if (cur.outside() || cur.distance(v) > 4)
					continue;
				Vector<PolyPixel> nvs = vertexMap.get(cur);
				if (nvs == null)
					continue;
				for (PolyPixel nv : nvs) {
					if (!nv.isFinal())
						nearVertexes.add(nv);
				}
			}
		}
		return nearVertexes;
	}
	
	private PolyPixel getNearVertex(PolyPixel v, Vector<PolyPixel> vertexes) {
		Pixel cur;
		PolyPixel result = null;
		int distance = Integer.MAX_VALUE;
		int curDistance;
		for (int dx = -3; dx < 4; dx++) {
			for (int dy = -3; dy < 4; dy++) {
				cur = new Pixel(v.x() + dx, v.y() + dy);
				curDistance = cur.distance(v);
				if (cur.outside() || curDistance > 4)
					continue;
				Vector<PolyPixel> nvs = vertexMap.get(cur);
				if (nvs != null && nvs.size() > 0 && nvs.firstElement().distance(v) < distance) {
					boolean same = false;
					for (PolyPixel vertex : vertexes) {
						if (vertex.distance(nvs.firstElement()) < 2) {
							same = true;
							break;
						}
					}
					if (!same) {
						result = nvs.firstElement();
						distance = curDistance;
					}
				}
			}
		}
		return result;
	}
	
	private void closeImage() {
		System.out.print("Closing image... ");
		Pixel p = new Pixel();
		p.x(0);
		for(int y = 1; y < (height - 1 - Pixel.BOTTOM_MARGIN) ; y++) {
			p.y(y);
			if (p.isBuilding())
				p.setRGB(Pixel.BORDER);
		}
		p.x(width - 1);
		for(int y = 1; y < (height - 1 - Pixel.BOTTOM_MARGIN) ; y++) {
			p.y(y);
			if (p.isBuilding())
				p.setRGB(Pixel.BORDER);
		}
		p.y(0);
		for(int x = 1; x < (width - 1) ; x++) {
			p.x(x);
			if (p.isBuilding())
				p.setRGB(Pixel.BORDER);
		}
		p.y(height - 1 - Pixel.BOTTOM_MARGIN);
		for(int x = 1; x < (width - 1); x++) {
			p.x(x);
			if (p.isBuilding())
				p.setRGB(Pixel.BORDER);
		}
		System.out.println("done");
	}
	
	public DataSet getData() {
	    return data;
	}
	
    public void getImage(double slon, double slat, double elon, double elat) throws Exception {
        double difLon = Math.abs(elon - slon);
        double difLat = Math.abs(elat - slat);
        height = (int)(difLat * pixelsEachDegree);
        width = (int)(difLon * pixelsEachDegree);
        BufferedImage tile;
        String url;
        
        img = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        Graphics2D g2 = img.createGraphics();
        
        double curSlon;
        double curSlat;
        double curElon;
        double curElat;
        
        int numRows = (int) Math.ceil((double) height / ((maxSize - 12)-12));
        int numCols = (int) Math.ceil((double) width / (maxSize-12));
        int curWidth;
        int curHeight;
        int x;
        int y;
        
        for (int curRow = 0; curRow < numRows; curRow++) {
            curSlat = slat + curRow * (maxSize - 12) * degreesEachPixel;
            curElat = slat + (curRow + 1) * (maxSize - 12) * degreesEachPixel;
            y = height - ((curRow + 1) * (maxSize - 12));
            if (y < 0)
                y = 0;
            if (curElat > elat) {
                curElat = elat;
                curHeight = height % (maxSize - 12);
                if (curHeight == 0)
                    curHeight = (maxSize - 12);
            }
            else
                curHeight = (maxSize - 12);
            
            for (int curCol = 0; curCol < numCols; curCol++) {
                curSlon = slon + curCol * maxSize * degreesEachPixel;
                curElon = slon + (curCol + 1) * maxSize * degreesEachPixel;
                x = curCol * maxSize;
                if (curElon > elon) {
                    curElon = elon;
                    curWidth = width % maxSize;
                    if (curWidth == 0)
                        curWidth = maxSize; 
                }
                else
                    curWidth = maxSize;
                url = String.format(new Locale("en", "UK"), wmsUrl, curSlon, curSlat - (12 * degreesEachPixel), curElon, curElat, curWidth, curHeight + 12);
                System.out.println("Downloading image " + curRow + "_" + curCol + ".png (curSlon: " + curSlon + ", curElon: " + curElon + ", curSlat: " + curSlat + ", curElat: " + curElat + ")");
                tile = ImageIO.read(new URL(url)).getSubimage(0, 0, curWidth, curHeight);
                g2.drawImage(tile, x, y, null);
            }
        }
    }
}
